/**
 * BridgeEngineCore
 * '_'下划线函数是内部调用
 */
class BridgeEngineCore {
    constructor() {
        // 存放H5调用 Native 函数的消息队列
        this.nativeMessageQueue = [];
        // 存放H5 register(注册) 提供Native 调用函数对应的响应回调函数
        this.registerMethodCallbacks = {};
        // 存放H5调用 Native 函数的回到函数，通过通过id对应
        this.webResponseCallbacks = {};
        this.uniqueId = 1;
    }

    /**
     * Core 函数
     * H5注册函数，提供给给Native调用
     * @param method 函数名称，Native通过该函数名称调用
     * @param handler 处理器，作用是接收Native传递过来的参数和响应Native，如：function(dataFromNative,success,fail,complete){}
     */
    register(method, handler) {
        this.registerMethodCallbacks[method] = handler;
    }

    /**
     * Core 函数
     * 设置默认的Handler用户接收，Native 给web发送的消息
     * @param handler
     */
    defaultHandler(handler) {
        this.registerMethodCallbacks['defaultHandler'] = handler;
    }

    /**
     * Core 函数
     * 调用Native注册函数，通过函数名调用
     * @param method 函数名
     * @param data 传递给Native的数据，
     * @param successCallback 接收本地调用反馈的回调函数，
     * @param failCallback 接收本地调用反馈的回调函数，
     * @param completeCallback 接收本地调用反馈的回调函数，
     */
    call(method, data, successCallback, failCallback, completeCallback) {
        //如果函数只有2个参数，第二个参数为function，就默认为successCallback 如：call("test",function(){})
        if (arguments.length === 2 && typeof data == 'function') {
            successCallback = data;
            data = null;
        }
        //如果函数只有3个参数，第二个参数为function，就默认为successCallback如：call("test",function(){},function(){})
        if (arguments.length === 3 && typeof data == 'function') {
            successCallback = data;
            failCallback = successCallback;
            data = null;
        }
        //如果函数只有4个参数，第二个参数为function，就默认为successCallback如：_call("test",function{},function(){},function(){})
        if (arguments.length === 4 && typeof data == 'function') {
            successCallback = data;
            failCallback = successCallback;
            completeCallback = failCallback;
            data = null;
        }
        let message = {
            method: method,
            data: data,
            success: successCallback,
            fail: failCallback,
            complete: completeCallback
        };
        // 兼容Android OS 旧版API,新本已经废弃该方法
        if (method === 'navigateBackByTag') {
            message.method = 'navigateBack'
        }

        // 兼容Android OS 旧版API。
        if (method === 'addMenu') {
            // Android OS 旧版API 传入的是一个Array字符串，新本API接收的是对象
            // 即转换message.data=[{"icon": add, "text": 添加1}] = >message.data="{"menus":[{"icon": "add", "text": "添加1"}]}"
            message.data = JSON.stringify({menus: JSON.parse(message.data)});
        }
        // 兼容Android OS 旧版API。
        if (method === 'removeMenu') {
            // Android OS 旧版API 传入的是一个Array字符串，新本API接收的是对象
            // 即转换message.data="添加" = >message.data="{"text":"添加"}"
            message.data = JSON.stringify({text: message.data});
        }
        // 即将发送消息给Native
        this.$doSend(message);
    }

    /**
     * Core 函数
     * H5给Native发送消息，此时没有method字段
     * @param data 传递给Native的数据，
     * @param successCallback 接收本地调用反馈的回调函数，
     * @param failCallback 接收本地调用反馈的回调函数，
     * @param completeCallback 接收本地调用反馈的回调函数，
     */
    send(data, successCallback, failCallback, completeCallback) {
        this.$doSend({
            data: data,
            success: successCallback,
            fail: failCallback,
            complete: completeCallback
        });
    }

    /**
     * Core 函数
     * 即将给Native发送消息
     * @param message 消息体
     */
    $doSend(message) {
        if (message === undefined || message === null) return;

        // 设置调用成功的回调函数ID
        if (message.success) {
            let successCallbackId = 'web_' + (this.uniqueId++) + '_' + new Date().getTime();
            this.webResponseCallbacks[successCallbackId] = message.success;
            message.successCallbackId = successCallbackId;
        }
        // 设置调用失败的回调函数ID
        if (message.fail) {
            let failCallbackId = 'web_' + (this.uniqueId++) + '_' + new Date().getTime();
            this.webResponseCallbacks[failCallbackId] = message.fail;
            message.failCallbackId = failCallbackId;
        }
        // 设置调用完成的回调函数ID
        if (message.complete) {
            let completeCallbackId = 'web_' + (this.uniqueId++) + '_' + new Date().getTime();
            this.webResponseCallbacks[completeCallbackId] = message.complete;
            message.completeCallbackId = completeCallbackId;
        }
        // 保存消息队列，防止消息丢失
        this.nativeMessageQueue.push(message);
        // 消息直接传递给本地
        if (jsBridge.platform === 'Android') {
            window.webkit.postMessageToAndroid(JSON.stringify(this.nativeMessageQueue));
            this.nativeMessageQueue = [];
        } else if (jsBridge.platform === 'IOS') {
            // 深度拷贝，移除function和一些不能转换成Json的数据
            this.nativeMessageQueue = JSON.parse(JSON.stringify(this.nativeMessageQueue));
            window.webkit.messageHandlers.iOS_Native_FlushMessageQueue.postMessage(this.nativeMessageQueue);
            this.nativeMessageQueue = [];
        }
    }

    /**
     * Core 函数
     * 构建 Native 回调函数的消息体
     * @param responseId Native中回调函数的id
     * @param method     Native调H5用的方法
     * @return CallBackFunction
     */
    $buildCallBackFunction(responseId, method) {
        if (!responseId) return undefined;
        let that = this;
        return function (responseData) {
            that.$doSend({
                responseId: responseId,
                method: method,
                data: responseData,
                messageType: "response"
            });
        }
    }

    /**
     * Core 函数
     * 调度 Native 发送过来的消息
     */
    $dispatchMessageFromNative(dataFromNative) {
        let message = JSON.parse(dataFromNative);
        // 如果消息类型为response(响应)，即H5调用Native时收到Native的响应
        if (message.messageType === 'response') {
            let responseCallback = this.webResponseCallbacks[message.responseId];
            if (responseCallback) {
                switch (message.method) {
                    // H5调用本地选择图片时，回调和H5的数据转换为对象
                    case 'chooseImage':
                        responseCallback(JSON.parse(message.data));
                        break;
                    //  H5调用图片转Base64，回调和H5的数据转换为对象
                    case 'imageToBase64':
                        responseCallback(JSON.parse(message.data));
                        break;
                    //  H5调用上传本地文件，回调和H5的数据转换为对象
                    case 'uploadFile':
                        responseCallback(JSON.parse(message.data));
                        break;
                    default:
                        responseCallback(message.data);
                        break;
                }

                // 回调完成就删除，因为每次调用都会注入新的function
                delete this.webResponseCallbacks[message.responseId];
            }
            return;
        }

        // 通过Native调用H5注册的函数名，获取H5中注册函数时的设置回调函数
        let handler = this.registerMethodCallbacks[message.method];
        if (handler) {
            let success = this.$buildCallBackFunction(message.successCallbackId, message.method);
            let fail = this.$buildCallBackFunction(message.failCallbackId);
            let complete = this.$buildCallBackFunction(message.completeCallbackId);
            // 如果Native 调用的函数名对应的H5回到函数为为空，即注册该函数并设置回调接口时，直接调用H5的回调函数
            handler(message.data, success, fail, complete);
            return;
        }
        /**
         *  在Native发送生命周期函数时，是没有在registerMethodCallbacks中注册的，所以要通过method控制发出不同的事件
         *  还有一种情况是，H5接收Native发送过来的消息，这时候method为空，如果H5需要接收Native发送的消息时
         *  调用jsBridge.defaultHandler(function(res，callBack){})时，会在registerMethodCallbacks中注册一个defaultHandler
         *  对应的回到函数。
         */
        switch (message.method) {
            case "onNativeLoad":
                /**
                 *  如果原生调用H5中jsBridge未注册的方法，且方法名为'onNativeLoad'的时候
                 *  且H5中通过window.onNativeLoad方式监听该函数就触发onNativeLoad
                 *  该函数用户原生界面初始化完成，给页面传递启动参数用的
                 */
                if (window.onNativeLoad) {
                    window.onNativeLoad(message.data);
                }
                break;
            case "onNativeResult":
                /**
                 * 如果原生调用H5中jsBridge未注册的方法，且方法名为'onNativeResult'的时候
                 * 且H5中通过window.onNativeResult方式监听该函数就触发onNativeResult
                 * 该函数用于原生传递上一个界面，返回的参数
                 */
                if (window.onNativeResult) {
                    window.onNativeResult(message.data);
                }
                break;
            case "onNativeMenuItemSelected":
                /**
                 * 如果原生调用H5中jsBridge未注册的方法，且方法名为'onNativeMenuItemSelected'的时候
                 * 且H5中通过window.onNativeMenuItemSelected方式监听该函数就触发onNativeMenuItemSelected
                 * 该函数传递原生menu点击事件，Android onOptionsItemSelected
                 */
                if (window.onNativeMenuItemSelected) {
                    window.onNativeMenuItemSelected(message.data);
                }
                break;
            case "onNativeBackListener":
                /**
                 * 如果原生调用H5中jsBridge未注册的方法，且方法名为'onNativeBackListener'的时候
                 * 且H5中通过window.onNativeBackListener方式监听该函数就触发onNativeBackListener
                 * 该函数传递原生返回键事件
                 */
                if (window.onNativeBackListener) {
                    window.onNativeBackListener();
                }
                break;
            case "onNativeDestroy":
                /**
                 * 如果原生调用H5中jsBridge未注册的方法，且方法名为'onNativeDestroy'的时候
                 * 且H5中通过window.onNativeDestroy方式监听该函数就触发onNativeDestroy
                 * 该函数传递原生返回键事件
                 */
                if (window.onNativeDestroy) {
                    window.onNativeDestroy();
                }
                break;
            default:
                let defaultHandler = this.registerMethodCallbacks["defaultHandler"];
                if (defaultHandler) {
                    let success = this.$buildCallBackFunction(message.successCallbackId, message.method);
                    let fail = this.$buildCallBackFunction(message.failCallbackId, message.method);
                    let complete = this.$buildCallBackFunction(message.completeCallbackId, message.method);
                    defaultHandler(message.data, success, fail, complete);
                } else {
                    let fail = this.$buildCallBackFunction(message.failCallbackId, message.method);
                    let complete = this.$buildCallBackFunction(message.completeCallbackId, message.method);
                    if (fail === undefined && complete === undefined) {
                        console.log("jsBridge: WARNING: no handler for message from Native（本次消息没有对应的处理处理程序）:" + dataFromNative);
                    } else {
                        let method = message.method || "unknown method";
                        let msg = {
                            msg: method + ":fail"
                        };
                        if (fail) fail(msg);
                        if (complete) complete(msg);
                    }
                }
                break;
        }
    }

    /**
     * Core 函数
     * 提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
     * @param dataFromNative  Native 传递过来的数据
     */
    $handleMessageFromNative(dataFromNative) {
        this.$dispatchMessageFromNative(dataFromNative);
    }

    /**
     * User 函数
     * 设置原生标题
     * @param params 参数
     */
    setTitle(params) {
        if (params === undefined) params = {};
        params.method = "setTitle";
        params.data = JSON.stringify(params);
        this.$doSend(params)
    }

    /**
     * User 函数
     * 保留当前页面，跳转到应用内的某个页面。
     * @param params 启动的参数
     */
    navigation(params) {
        if (params === undefined) params = {};
        params.method = "navigation";
        params.data = JSON.stringify(params);
        this.$doSend(params)
    }

    /**
     * User 函数
     *  关闭当前页面，跳转到应用内的某个页面。
     * @param params 启动的参数
     */
    redirect(params) {
        if (params === undefined) params = {};
        params.method = "redirect";
        params.data = JSON.stringify(params);
        this.$doSend(params)
    }

    /**
     * User 函数
     *  关闭所有页面，打开到应用内的某个页面。
     * @param params 启动的参数
     */
    reLaunch(params) {
        if (params === undefined) params = {};
        params.method = "reLaunch";
        params.data = JSON.stringify(params);
        this.$doSend(params)
    }

    /**
     * User 函数
     *  关闭当前页面，返回上一页面或多级页面。
     * @param options 参数
     * delta  number 返回的页面数，如果 delta 大于现有页面数，则返回到首页。
     * tag    String  delta 同时存在，tag优先级高于delta，如果tag指定的界面不存在，则返回失败。
     * params Object  传递给上一个界面的参数。
     */
    navigateBack(options) {
        if (options === undefined) options = {};
        options.method = "navigateBack";
        options.data = JSON.stringify(options);
        this.$doSend(options)
    }

    /**
     * User 函数
     * 设置返回上一级的参数，优先级最高，支持：原生返回、侧滑、navigateBack
     * @param options 参数
     */
    setResult(options) {
        if (options === undefined) options = {};
        options.data = JSON.stringify(options);
        options.method = "setResult";
        this.$doSend(options)
    }

    /**
     * User 函数
     * 监听系统返回键，Android OS，标题栏返回键，物理返回键、IOS OS标题栏返回键
     * 监听返回键时，禁用侧滑关闭
     * @param options 参数
     */
    addBackListener(options) {
        if (options === undefined) options = {};
        options.data = JSON.stringify(options);
        options.method = "addBackListener";
        this.$doSend(options)
    }

    /**
     * User 函数
     * 在标题栏中添加menus，不能添加text相同的menu，即菜单名称不能相同，相同添加无效
     * @param options 参数
     */
    addMenu(options) {
        if (options === undefined) options = {};
        options.data = JSON.stringify(options);
        options.method = "addMenu";
        this.$doSend(options)
    }

    /**
     * User 函数
     * 除当前menus中指定的menu，如果指定的menu不存在，返回调用失败
     * @param options 参数
     */
    removeMenu(options) {
        if (options === undefined) options = {};
        options.data = JSON.stringify(options);
        options.method = "removeMenu";
        this.$doSend(options)
    }

    /**
     * User 函数
     * 移除当前标题栏中所有的菜单
     * @param options 参数
     */
    removeAllMenu(options) {
        if (options === undefined) options = {};
        options.data = JSON.stringify(options);
        options.method = "removeAllMenu";
        this.$doSend(options)
    }

    /**
     * User 函数
     * 图片选择
     * @param params
     * @private
     */
    chooseImage(params) {
        // 如果H5调用chooseImage不传任何参数时，为params设置默认值
        if (params === undefined) {
            params = {
                count: 9,
                sizeType: ['original', 'compressed'],
                sourceType: ['album', 'camera'],
            };
        } else {
            if (params.sizeType === undefined) {
                params.sizeType = ['original', 'compressed']
            }
            if (params.sourceType === undefined) {
                params.sourceType = ['album', 'camera']
            }
            if (params.count === undefined) {
                params.count = 9
            }
        }
        // 设置Native 注册的函数名称
        params.method = "chooseImage";
        params.data = JSON.stringify(params);
        this.$doSend(params)
    }

    /**
     * User 函数
     * 图片转Base64
     * @param params
     * @private
     */
    imageToBase64(params) {
        if (params === undefined) {
            params = {
                quality: 80,
            }
        } else if (params.quality === undefined) {
            params.quality = 80
        }
        // 如果H5调用chooseImage不传任何参数时，为params设置默认值
        // 设置Native 注册的函数名称
        params.method = "imageToBase64";
        params.data = JSON.stringify(params);
        this.$doSend(params)
    }

    /**
     * User 函数
     * 文件上传
     * @param params
     * @private
     */
    uploadFile(params) {
        if (params === undefined) {
            params = {
                url: "",
                filePath: "",
                name: "",
                header: {},
                formData: {},
            }
        } else {
            if (params.url === undefined) {
                params.url = ""
            }
            if (params.filePath === undefined) {
                params.filePath = ""
            }
            if (params.name === undefined) {
                params.name = ""
            }
            if (params.header === undefined) {
                params.header = {}
            }
            if (params.formData === undefined) {
                params.formData = {}
            }
        }
        params.method = "uploadFile";
        params.data = JSON.stringify(params);
        this.$doSend(params);
    }

    /**
     * User 函数
     * 扫码，支持一维码和二维码
     * @param params
     * @private
     */
    scanCode(params) {
        if (params === undefined) {
            params = {
                onlyFromCamera: false,
                scanType: ['barCode', qrCode]
            }
        } else {
            if (params.onlyFromCamera === undefined) {
                params.onlyFromCamera = false
            }
            if (params.scanType === undefined) {
                params.scanType = ['barCode', qrCode]
            }
        }
        params.method = "scanCode";
        params.data = JSON.stringify(params);
        this.$doSend(params);
    }
}

(function ($) {
        if (window.jsBridge) return;
        /**
         * 监听JavaScript是否有报错
         */
        window.addEventListener("error", function (msg, url, line) {
            console.log("jsBridge html error:" + JSON.stringify(msg));
        });
        window.jsBridge = new BridgeEngineCore();
        jsBridge.platform = $.platform;
        if (jsBridge.platform === 'Android') {
            /**
             * Android 系统在9.0以后不能再webView的URL没有加载时不能先注入JavaScript脚本，因为webView环境参数的window对象不一样
             * 所以只能在网页加载完成后才能注入JavaScript脚本，脚本注入完成后直接执行onNativeBridgeReady 函数
             */
            if (window.onNativeBridgeReady) {
                window.onNativeBridgeReady(jsBridge);
            }
        } else {
            /**
             * IOS 可以在webView没有加载url时提前注入JavaScript脚本
             * 当监听网页加载完成时调用自动调用本地函数，
             */
            window.addEventListener("load", function () {
                if (jsBridge.platform === 'IOS') {
                    // 当网页加载完成，通知Native 开始发送消息队列
                    window.webkit.messageHandlers.iOS_Native_startupMessageQueue.postMessage(null);
                }
                if (window.onNativeBridgeReady) {
                    window.onNativeBridgeReady(window.jsBridge);
                }
            });
        }
        console.log("---------->通信脚本注入完成 --- 系统平台:" + jsBridge.platform);
    }
)(window);
